#ifndef AVALON_SERVICELOCATORCLIENT_HPP
#define AVALON_SERVICELOCATORCLIENT_HPP
#include "Avalon/IO/Connection.hpp"
#include "Avalon/IO/ConnectionState.hpp"
#include "Avalon/ServiceLocator/DirectoryEntry.hpp"
#include "Avalon/ServiceLocator/Directory.hpp"
#include "Avalon/ServiceLocator/ServiceLocatorServices.hpp"
#include "Avalon/Services/ServiceNode.hpp"
#include "Avalon/SignalHandling/TaskSignalHandler.hpp"

namespace Avalon {
namespace ServiceLocator {

  /*! \class ServiceLocatorClient
   *  \brief Client used to locate services on a service network.
   */
  class ServiceLocatorClient : public IO::Connection {
    public:

      //! Signals whether a service is available.
      /*!
        \param serviceEntry The service entry.
        \param available <code>true</code> iff the service is available.
      */
      typedef boost::signals2::signal<void (const ServiceEntry&, bool)>
        ServiceSignal;

      //! Constructs a ServiceLocatorClient.
      /*!
        \param channel The Channel that connects to the ServiceLocatorServer.
        \param dataShuttleFactory The type of serialization to use.
        \param heartbeatTimer The Timer used for heartbeats.
        \param timeout The Connection timeout.
        \param threadPool The ThreadPool used for requests.
      */
      ServiceLocatorClient(IO::Channel* channel,
        Serialization::DataShuttleFactory* dataShuttleFactory,
        Threading::Timer* heartbeatTimer,
        boost::posix_time::time_duration timeout,
        Threading::ThreadPool& threadPool);

      virtual ~ServiceLocatorClient();

      //! Returns the Tag of the Account that's logged in.
      DirectoryEntry::Tag GetAccountTag() const;

      //! Returns the raw session id.
      std::string GetSessionId() const;

      //! Encrypts the session id.
      /*!
        \param key The encryption key.
        \return The session id encrypted using the specified encryption
                <i>key</i>.
      */
      std::string GetEncryptedSessionId(unsigned int key) const;

      //! Logs onto the ServiceLocatorServer.
      /*!
        \param username The username.
        \param password The password.
      */
      void Login(const std::string& username, const std::string& password);

      //! Authenticates a session.
      /*!
        \param sessionId An encoded session id.
        \param key The encryption key used to encode the <i>sessionId</i>.
        \return The Tag of the Account using the specified <i>sessionId</i>.
      */
      DirectoryEntry::Tag AuthenticateSession(const std::string& sessionId,
        unsigned int key);

      //! Locates a service.
      /*!
        \param name The name of the service to locate.
        \return The list of services that were located.
      */
      std::vector<ServiceEntry> Locate(const std::string& name);

      //! Subscribes to service notifications.
      /*!
        \param name The name of the service to subscribe to.
        \return A connection to service notifications for services with the
                specified <i>name</i>.
      */
      boost::signals2::connection Subscribe(const std::string& name,
        const ServiceSignal::slot_type& slot);

      //! Subscribes to service notifications.
      /*!
        \param name The name of the service to subscribe to.
        \param services [out] The list of services currently available.
        \return A connection to service notifications for services with the
                specified <i>name</i>.
      */
      boost::signals2::connection Subscribe(const std::string& name,
        const ServiceSignal::slot_type& slot,
        std::vector<ServiceEntry>* services);

      //! Registers a service.
      /*!
        \param name The name of the service.
        \param details Any additional details about the service.
        \return The ServiceEntry corresponding to the registered service.
      */
      ServiceEntry Register(const std::string& name,
        const std::string& details);

      //! Creates an Account.
      /*!
        \param name The name of the Account.
        \param directory The Account's directory.
        \param password The Account's password.
        \return The Tag representing the new Account.
      */
      DirectoryEntry::Tag CreateAccount(const std::string& name,
        const DirectoryEntry::Tag& directory, const std::string& password);

      //! Loads a DirectoryEntry.
      /*!
        \param id The id of the DirectoryEntry to load.
        \return The DirectoryEntry with the specified <i>id</i>.
      */
      DirectoryEntry* LoadDirectoryEntry(unsigned int id);

      //! Loads the parent Directory of a DirectoryEntry.
      /*!
        \param id The id of the DirectoryEntry whose parent is to be loaded.
        \return The parent of the DirectoryEntry with the specified <i>id</i>.
      */
      Directory* LoadParentDirectory(unsigned int id);

      //! Creates a Directory.
      /*!
        \param name The name of the directory.
        \param parent The parent directory.
      */
      void CreateDirectory(const std::string& name,
        const DirectoryEntry::Tag& parent);

      //! Moves a DirectoryEntry.
      /*!
        \param tag The Tag of the DirectoryEntry to move.
        \param directory The Directory to move the DirectoryEntry to.
      */
      void MoveEntry(const DirectoryEntry::Tag& tag,
        const DirectoryEntry::Tag& directory);

      //! Deletes an DirectoryEntry.
      /*!
        \param tag The Tag of the entry to delete.
      */
      void DeleteEntry(const DirectoryEntry::Tag& tag);

      //! Sets an Account's Permissions for a DirectoryEntry.
      /*!
        \param account The Account Tag whose Permissions are to be set.
        \param entry The Tag of the Entry to set the <i>account</i>'s
               Permissions for.
        \param permissions The Permissions to grant to the <i>account</i> for
               the specified <i>entry</i>.
      */
      void SetPermissions(const DirectoryEntry::Tag& account,
        const DirectoryEntry::Tag& entry, Directory::Permission permissions);

      virtual bool IsOpen() const;

      virtual void Close();

      virtual boost::signals2::connection ConnectClosedSignal(
        const ClosedSignal::slot_type& slot) const;

      virtual boost::signals2::connection ConnectClosedSignal(
        const ClosedSignal::slot_type& slot, Out<bool> isOpen) const;

    private:
      struct ServiceSignalEntry;
      mutable boost::mutex m_mutex;
      boost::scoped_ptr<IO::Channel> m_channel;
      IO::ConnectionState m_channelState;
      bool m_isOpen;
      Services::ServiceNode m_service;
      DirectoryEntry::Tag m_accountTag;
      std::string m_sessionId;
      std::map<unsigned int, DirectoryEntry*> m_entries;
      std::map<unsigned int, Directory*> m_parentEntries;
      std::map<std::string, ServiceSignalEntry*> m_serviceEntries;
      boost::condition_variable m_closedCondition;
      mutable boost::shared_ptr<ClosedSignal> m_closedSignal;
      SignalHandling::TaskSignalHandler m_signalHandler;

      boost::signals2::connection InternalSubscribe(const std::string& name,
        const ServiceSignal::slot_type& slot,
        std::vector<ServiceEntry>* services,
        boost::unique_lock<boost::mutex>& lock);
      void InternalClose();
      DirectoryEntry* InternalLoadDirectoryEntry(unsigned int id,
        boost::unique_lock<boost::mutex>& lock);
      void Shutdown();
      void OnConnectionClosed();
      void OnServiceAvailability(IO::Channel* channel,
        const ServiceEntry& service, bool available);
      void OnDirectoryEntryMoved(IO::Channel* channel,
        const DirectoryEntry::Tag& entry,
        const DirectoryEntry::Tag& targetDirectory, bool added);
  };
}
}

#endif // AVALON_SERVICELOCATORCLIENT_HPP
